﻿using System;
using System.Buffers;
using System.Threading;

namespace eNets.IO.Buf;

sealed class SafeBuffer<T> : IBufferWriter<T>, IBuffer<T>
{
    public SafeBuffer(BufferOptions<T> options)
    {
        this.spinLock = new SpinLock();
        this.unsafeBuffer = new Buffer<T>(options);
    }

    private SpinLock spinLock;
    private IBuffer<T> unsafeBuffer;

    public object Token
    {
        get { return this.unsafeBuffer.Token; }
        set { this.unsafeBuffer.Token = value; }
    }
    
    public long ReadableLength
    {
        get
        {
            var lockToken = false;
            try
            {
                this.spinLock.Enter(ref lockToken);
                return this.unsafeBuffer.ReadableLength;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (lockToken)
                {
                    this.spinLock.Exit();
                }
            }
        }
    }
    
    public Memory<T> GetMemory(int sizeHint = 0)
    {
        return this.GetWritableSequence(sizeHint);
    }

    public Span<T> GetSpan(int sizeHint = 0)
    {
        return this.GetMemory(sizeHint).Span;
    }
    
    public void Advance(int count)
    {
        this.WriterAdvance(count);
    }
    
    public ReadOnlySequence<T> GetReadableSequence()
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            return this.unsafeBuffer.GetReadableSequence();
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }

    public void ReaderAdvanceTo(SequencePosition consumed)
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            this.unsafeBuffer.ReaderAdvanceTo(consumed);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }

    public void ReaderAdvanceTo(SequencePosition consumed, SequencePosition examined)
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            this.unsafeBuffer.ReaderAdvanceTo(consumed, examined);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }

    public Memory<T> GetWritableSequence(int size)
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            return this.unsafeBuffer.GetWritableSequence(size);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }

    public void WriterAdvance(int count)
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            this.unsafeBuffer.WriterAdvance(count);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }

    public void Flush()
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            this.unsafeBuffer.Flush();
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }
    
    public void Reset()
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            this.unsafeBuffer.Reset();
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }

    public void Clear()
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            this.unsafeBuffer.Clear();
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }

    public void Release()
    {
        var lockToken = false;
        try
        {
            this.spinLock.Enter(ref lockToken);
            this.unsafeBuffer.Release();
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (lockToken)
            {
                this.spinLock.Exit();
            }
        }
    }
}